<template>
  <h3>通过 SFU(mediasoup) 实现 WebRTC 视频通话</h3>
  <div id="buttons">
    <input v-model="numPeopleInput" type="text" placeholder="请输入人数" maxlength="48" />
    <input v-model="roomIdInput" type="text" placeholder="请输入房间号" maxlength="48" />
    <button @click="joinRoom">加入</button>
    <button @click="leaveRoom">离开</button>
  </div>
  <div class="videos">
    <!-- 本地窗口-动态将本地视频组件插入到远端视频的第一个位置 -->
    <!-- 远端窗口 -->
    <div ref="remoteVideo" class='remoteFlex'></div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import io from 'socket.io-client'
import * as mc from "../../common/mediasoup-client-3/src/index"

let uid = Math.random().toString(36).substr(2)
let uName = ref(null)
const numPeopleInput = ref(null)
const roomIdInput = ref(null)
let roomName



const isVideoVisible = ref(false)

const localVideo = ref(null)
const remoteVideo = ref(null)

// 创建socketIO 连接
let socket = io("ws://127.0.0.1:8099")
let device      //设备
let rtpCapabilities  //rtp功能
let producerT  //生产者发送通道

let consumerIdArr = [] //消费者数组[{用户uid:新生产者newProducerId}]
let consumerTArr = [] //消费通道数组
let audioProducer
let videoProducer

let audioParams

let params = {
  // mediasoup params
  encodings: [
    { rid: 'r0', maxBitrate: 100000, scalabilityMode: 'S1T3' },
    { rid: 'r1', maxBitrate: 300000, scalabilityMode: 'S1T3' },
    { rid: 'r2', maxBitrate: 900000, scalabilityMode: 'S1T3' },
  ],
  codecOptions: { videoGoogleStartBitrate: 1000 }
}
let videoParams = { params }


const joinRoom = async () => {
  let maxPeople = numPeopleInput.value ? parseInt(numPeopleInput.value) : 80; // 如果没输入人数，默认为80
  roomName = roomIdInput.value
  uName.value = uid
  if (!roomName) return alert("请输入房间号")
  try {
    //先--确认房间人数是否满
    await socket.emit('full', { uid, roomName }, async ({ data }) => {
      // console.log(data)
      if (data == '该房间人数已满') {
        alert("该房间人数已满")
      } else if (data == '您已经在此房间中') {
        alert("您已经在此房间中")
      } else {
        // 显示本地视频
        isVideoVisible.value = true
        // 加载本地视频流
        await getLocalStream()
        // 动态将本地视频组件插入到远端视频的第一个位置
        const localElem = document.createElement('div');
        localElem.classList.add('local');
        localElem.innerHTML = `<video autoplay muted class="localVideo"></video>
                              <div class="uName">${uName.value}</div> `
        localElem.querySelector('video').srcObject = localVideo.value.srcObject;

        if (remoteVideo.value) {
          remoteVideo.value.insertBefore(localElem, remoteVideo.value.firstChild);
        }
        //再--给后端发送加入房间
        socket.emit('joinRoom', { uid, roomName, maxPeople })
      }
    })
  } catch (error) {
    console.error('获取本地视频流失败', error)
  }
}

// Vue 生命周期钩子，页面挂载时执行
onMounted(() => {
  localVideo.value = document.createElement('video');
});

// 离开房间
const leaveRoom = () => {
  if (audioProducer) audioProducer.close()
  if (videoProducer) videoProducer.close()

  consumerTArr.forEach(consumerData => {
    consumerData.consumer.close()
    consumerData.consumerT.close()
  })

  consumerTArr = []
  consumerIdArr = []

  if (localVideo.value.srcObject) {
    localVideo.value.srcObject.getTracks().forEach(track => track.stop())
    localVideo.value.srcObject = null
  }

  remoteVideo.value.innerHTML = ''
  socket.emit('leaveRoom', { uid, roomName })
  socket.disconnect()
}



// ----------------------------------------------
//02--监听房间路由的Rtp功能
socket.on('getRtpCap', (rtpCap) => {
  // console.log(`02获取房间路由的Rtp功能：`, rtpCap)
  rtpCapabilities = rtpCap
  if (!device) { creDev() }
})


//监听有新的生产者刚刚加入
socket.on('new-producer', ({ newProducerIdObj }) => signalNewConsumerT(newProducerIdObj))

//监听生产者关闭
socket.on('producerT-close', ({ newProducerId }) => {
  // 当生产者关闭时收到服务器通知，我们需要关闭其他消费者与他的相关传输
  const producerToClose = consumerTArr.find(transportData => transportData.producerId === newProducerId)
  producerToClose.consumerT.close()
  producerToClose.consumer.close()

  // 从列表中删除该消费者通道
  consumerTArr = consumerTArr.filter(transportData => transportData.producerId !== newProducerId)

  // 删除视频div组件
  remoteVideo.value.removeChild(document.getElementById(`new-${newProducerId}`))
})

// ----------------------------------------------------------

// 01--获取本地视频流
const getLocalStream = () => {
  return new Promise((resolve, reject) => {
    navigator.mediaDevices.getUserMedia({
      audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },
      video: true
    }).then((stream) => {
      localVideo.value.srcObject = stream
      audioParams = { track: stream.getAudioTracks()[0], ...audioParams }
      videoParams = { track: stream.getVideoTracks()[0], ...videoParams }
      // console.log("01获取本地视频流成功")
      resolve()
    }).catch(error => {
      console.log("获取本地视频流失败", error)
      reject(error)
    })
  })
}

// 03---创建设备并加载RTP功能
const creDev = async () => {
  try {
    if (!device || device.loaded === false) {
      device = new mc.Device()
      // 给设备加载RTP功能----使用路由的RTP功能的设备进行加载
      await device.load({ routerRtpCapabilities: rtpCapabilities })
      // console.log('03设备的Rtp功能', device.rtpCapabilities)
      creSendT()
    }
  } catch (error) {
    console.log('创建设备失败', error)
  }
}

// 04---创建生产者发送通道
const creSendT = () => {
  if (!audioParams || !videoParams) {
    console.log('音视频参数未准备好')
    return
  }
  // sender是true，让后端知道是生产者
  socket.emit('creWebRtcT', { sender: true }, ({ paramsT }) => {
    if (paramsT.error) {
      console.log(paramsT.error)
      return
    }
    // console.log('04后端传来生产者通道的参数', paramsT)

    // 由设备创建一个生产者发送通道，并携带后端传过来的通道参数
    producerT = device.createSendTransport(paramsT)
    // ----连接事件，dtls参数，正确回调，出错回调
    producerT.on('connect', async ({ dtlsParameters }, callback, errback) => {
      try {
        // 生产者连接通道的方法：发送 paramsT里的DTLS参数
        await socket.emit('producerT-connect', { dtlsParameters, })
        // 正确回调
        callback()
      } catch (error) {
        // 出错回调
        errback(error)
      }
    })

    // ----生产事件，参数，正确回调，出错回调
    producerT.on('produce', async (parameters, callback, errback) => {
      // console.log(`05生产事件，生产者的${parameters.kind}参数`, parameters)
      try {
        // 生产者发送通道的方法
        await socket.emit('producerT-produce', {
          kind: parameters.kind,
          rtpParameters: parameters.rtpParameters,
          appData: parameters.appData,
        }, ({ id, producerExist }) => {
          // 从服务器端返回生产者的 ID，和 存在生产者
          callback({ id })
          // 存在生产者，获取所有生产者连接通道
          if (producerExist) getProducersT()
        })
      } catch (error) { errback(error) }
    })

    conSendT()
  })
}

// 05---生产者连接发送通道
const conSendT = async () => {
  // 此操作将触发上面的“连接”和“生产”事件
  audioProducer = await producerT.produce(audioParams)
  videoProducer = await producerT.produce(videoParams)

  // 获取房间内的其他生产者
  getProducersT()

  audioProducer.on('trackended', () => {
    console.log('音频轨道结束')
  })
  audioProducer.on('transportclose', () => {
    console.log('音频通道关闭')
  })
  videoProducer.on('trackended', () => {
    console.log('视频轨道结束')
  })
  videoProducer.on('transportclose', () => {
    console.log('视频通道关闭')
  })
}

// 获取所有生产者连接通道
const getProducersT = () => {
  socket.emit('getProducersT', (producerUid, producerAVIds, producerUidAVIds) => {
    // 通道数 = （人数-1）* 2  // 人数 = 通道数/2 + 1 = 端口数开方 - 1。
    // console.log('获取到的生产者列表:', producerUidAVIds)
    // 为每个生产者创建一个数组（由音视频两个通道id构成）
    producerUidAVIds.forEach(signalNewConsumerT)
    // producerAVIds.forEach(signalNewConsumerT)
    // producerAVIds.forEach(signalNewConsumerT(newProducerId, uid))
    // producerUid.forEach(signalNewConsumerT)
  })
}

// 06---创建消费者接收通道
const signalNewConsumerT = async (newProducerIdObj) => {
  // console.log(`newProducerId---： ${(newProducerId)}`, typeof newProducerId)

  // 将对象转换为数组（键值对数组）  
  let objectToArray = Object.entries(newProducerIdObj);
  //对于新的消费者，需要加载其他生产者音视频和用户名
  let newUName
  let newProducerId
  // 先用数组的includes()方法检查consumerIdArr数组中是否已经包含了newProducerId
  if (consumerIdArr.includes(objectToArray[0][1])) return
  if (objectToArray.length == 2) {
    //老人收到的，长度=2
    newUName = objectToArray[1][1]
    // console.log(`老人收到的,${newUName}：${objectToArray[0][1]}`)
  } else {
    //新人收到的，长度=1
    newUName = objectToArray[0][0]
    // console.log(`新人收到的,${newUName}：${objectToArray[0][1]}`)
  }
  newProducerId = objectToArray[0][1]
  // 新的生产者加入消费者id数组里
  consumerIdArr.push(newProducerId)


  //作为消费者创建webRTC通道，给后端发送创建webRTC通道事件
  await socket.emit('creWebRtcT', { consumer: true }, ({ paramsT }) => {
    if (paramsT.error) {
      console.log(paramsT.error)
      return
    }
    // console.log(`06后端传来消费者者通道参数： ${paramsT}`)

    // 由设备创建一个消费者接收通道，并携带后端传过来的通道参数
    let consumerT = device.createRecvTransport(paramsT)

    // ----连接事件，dtls参数，正确回调，出错回调
    consumerT.on('connect', async ({ dtlsParameters }, callback, errback) => {
      try {
        // 消费者连接接收通道的方法：发送 paramsT里的DTLS参数
        socket.emit('consumerT-connect', {
          dtlsParameters,
          consumerTId: paramsT.id,
        })
        callback()
      } catch (error) { errback(error) }
    })

    conRecvT(consumerT, newProducerId, newUName, paramsT.id)
  })
}


// 07---消费者连接接收通道
const conRecvT = async (consumerT, newProducerId, newUName, consumerTId) => {
  //给服务器发送消费者的消耗事件，发送rtp功能并返回通道参数
  await socket.emit('consumerT-consume', {
    rtpCapabilities: device.rtpCapabilities, newProducerId, consumerTId,
  }, async ({ paramsT }) => {
    if (paramsT.error) {
      console.log('消费通道出错', paramsT.error)
      return
    }

    // console.log(`07后端消耗通道参数： ${paramsT}`)
    // 创建一个消费者，用本地消费者传输进行消费
    const consumer = await consumerT.consume({
      id: paramsT.id,
      producerId: paramsT.producerId,
      kind: paramsT.kind,
      rtpParameters: paramsT.rtpParameters
    })
    // 新消费者通道加入组
    consumerTArr.push({
      consumerT,
      consumerTId: paramsT.id,
      producerId: newProducerId,
      consumer,
    })

    // 为新消费者媒体创建一个新的 div 元素
    const newElem = document.createElement('div')
    newElem.setAttribute('id', `new-${newProducerId}`)
    newElem.setAttribute('class', 'remote');

    if (paramsT.kind == 'audio') {
      //附加到音频容器
      newElem.innerHTML = '<audio id="' + newProducerId + '" autoplay></audio>'
    } else {
      // 视频容器和用户名容器
      newElem.innerHTML = `<video id="${newProducerId}" autoplay class="remoteVideo"></video>
                           <div class="uName">${newUName}</div>`
    }

    remoteVideo.value.appendChild(newElem)

    //解构并从制作者处检索视频轨道
    const { track } = consumer
    //新的窗口组件加载码流
    document.getElementById(newProducerId).srcObject = new MediaStream([track])

    // 08 --消费者恢复事件（消费者在paused暂停后启动）
    socket.emit('consumer-resume', { consumerId: paramsT.consumerId })
  })
}
</script>

<style>
button {
  margin: 2px;
}

.videos {
  display: flex;
}

.local {
  width: 360px;
  margin: 4px 4px 4px 4px;
}

.uName {
  position: absolute;
  text-align: center;
  width: 360px;
  height: 26px;
  margin-top: -30px;
  background-color: rgba(0, 0, 0, 0.1);
  color: white;

}

.localVideo {
  width: 360px;
}

.remoteFlex {
  display: flex;
  flex-wrap: wrap;
}

.remote {
  margin: 4px;
}

.remoteVideo {
  width: 360px;
}
</style>